package com.example.geometryshader;

import android.content.Context;
import android.opengl.GLES32;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.Matrix;

public class GLRender implements Renderer {

    private final String LOG_TAG = "GLRender";
    private int[] vaoIds = new int[1];  // 用于存储 VAO ID
    private int[] vboIds = new int[1];
    private int[] eboIds = new int[1];
    private int shaderProgramId;
    // 着色器 ID
    private FloatBuffer boxVerticesBuffer;
    private IntBuffer boxIndicesBuffer;

    private float[] m_modelMatrix = new float[16];
    private float[] m_viewMatrix = new float[16];
    private float[] m_projectionMatrix = new float[16];

    private long startTime;

    public int width = 1920;
    public int height = 1080;


    private final String vsCode = "#version 320 es\n" +
            "#extension GL_EXT_shader_io_blocks : enable\n" +
            "layout(location = 0) in vec3 a_Position;\n" +
            "layout(location = 1) in vec3 a_Color;\n" +

            "out VS_OUT {\n" +
            "    vec3 v_color;\n" +
            "    vec3 v_world_pos;\n" +
            "} vs_out;\n" +

            "void main() {\n" +
            "    vs_out.v_color = a_Color;\n" +
            "    vs_out.v_world_pos = a_Position;\n" +
            "    gl_Position = vec4(a_Position, 1.0);\n" +
            "}";

    private final String gsCode = "#version 320 es\n" +
            "#extension GL_EXT_shader_io_blocks : enable\n" +
            "layout (triangles) in;\n" +
            "layout (triangle_strip, max_vertices = 3) out;\n" +

            "uniform mat4 u_ModelMatrix;\n" +
            "uniform mat4 u_ViewMatrix;\n" +
            "uniform mat4 u_ProjMatrix;\n" +

            "in VS_OUT {\n" +
            "    vec3 v_color;\n" +
            "    vec3 v_world_pos;\n" +
            "} gs_in[];\n" +

            "uniform float u_time;\n" +

            "out vec3 v_color; \n" +
            "vec4 explode(vec3 world_pos, vec3 normal)\n" +
            "{\n" +
            "    float magnitude = 2.0;\n" +
            "    vec3 direction = - normal * ((sin(u_time) + 1.0) / 2.0) * magnitude; \n" +
            "    vec3 new_world_pos =  world_pos + direction;\n" +
            "    vec4 new_proj_pos = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * vec4(new_world_pos, 1.0);\n" +
            "    return new_proj_pos;\n" +
            "}\n" +

            "vec3 GetNormalInWorld()\n" +
            "{\n" +
            "    vec3 a = vec3(gs_in[0].v_world_pos) - vec3(gs_in[1].v_world_pos);\n" +
            "    vec3 b = vec3(gs_in[2].v_world_pos) - vec3(gs_in[1].v_world_pos);\n" +
            "    vec3 normal = normalize(cross(a, b));\n" +
            "    return normal;\n" +
            "}\n" +

            "void main() {    \n" +
            "    vec3 normal = GetNormalInWorld();\n" +

            "    gl_Position = explode(gs_in[0].v_world_pos, normal);\n" +
            "    v_color = gs_in[0].v_color;\n" +
            "    EmitVertex();\n" +
            "    gl_Position = explode(gs_in[1].v_world_pos, normal);\n" +
            "    v_color = gs_in[1].v_color;\n" +
            "    EmitVertex();\n" +
            "    gl_Position = explode(gs_in[2].v_world_pos, normal);\n" +
            "    v_color = gs_in[2].v_color;\n" +
            "    EmitVertex();\n" +
            "    EndPrimitive();\n" +
            "}";

    private final String fsCode = "#version 320 es\n" +
            "precision mediump float;\n" +
            "out vec4 fragColor;\n" +
            "in vec3 v_color;\n" +
            "void main() {\n" +
            "    fragColor = vec4(v_color, 1.0); \n" +
            "}";

    public GLRender(Context context)
    {

    }

    @Override
    public void onDrawFrame(GL10 gl)
    {
        // Log.d(LOG_TAG, "--------onDrawFrame-------");
        drawFrame();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height)
    {
        Log.i(LOG_TAG, "onSurfaceChanged:: w: " + width + " h: " + height);
        this.width = width;
        this.height = height;
        GLES32.glViewport(0,0,width,height);
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        Log.i(LOG_TAG, "opengl version: " + gl10.glGetString(GL10.GL_VERSION));

        GLES32.glClearColor(0.2F, 0.2F, 0.2F, 1.0F);      // 不要不写小数点！否则会没用。
        GLES32.glEnable(GL10.GL_DEPTH_TEST);

        initResources();

        // 记录程序启动时的时间
        startTime = System.currentTimeMillis();
    }

    /**
     * @brief 生成顶点数据
     */
    private void generateVertices()
    {
        boxVerticesBuffer = GLBufferWrapper.WrapFloat(new float[]{
                // position           // color
                -1.0f, -1.0f, -1.0f,  0.2f, 0.4f, 0.0f,                  // 顶点 0 (左下后)
                1.0f, -1.0f, -1.0f,   0.0f, 1.0f, 0.0f,                  // 顶点 1 (右下后)
                1.0f, -1.0f,  1.0f,   0.0f, 0.0f, 1.0f,                  // 顶点 2 (右下前)
                -1.0f, -1.0f,  1.0f,  0.0f, 0.7f, 1.0f,                  // 顶点 3 (左下前)
                -1.0f,  1.0f, -1.0f,  0.7f, 0.2f, 0.0f,                  // 顶点 4 (左上后)
                1.0f,  1.0f, -1.0f,   0.0f, 1.0f, 0.0f,                  // 顶点 5 (右上后)
                1.0f,  1.0f,  1.0f,   0.0f, 0.3f, 0.0f,                  // 顶点 6 (右上前)
                -1.0f,  1.0f,  1.0f,  0.1f, 0.2f, 0.6f,                  // 顶点 7 (左上前)
        });

        //  Note: 在几何着色器的gl_in[]下标和float[]数组下标的对于关系：gl_in[0] = float[1],gl_in[1] = float[0[, gl_in[2] = float[2]
        boxIndicesBuffer = GLBufferWrapper.WrapInt(new int[] {
                0, 1, 2, 0, 2, 3,   // 底面
                4, 6, 5, 4, 7, 6,   // 顶面
                0, 5, 1, 0, 4, 5,   // 后面
                2, 7, 3, 2, 6, 7,   // 前面
                0, 3, 7, 0, 7, 4,   // 左面
                1, 6, 2, 1, 5, 6    // 右面
        });
    }

    /**
     * @brief 初始化opengl es所需资源
     */
    private void initResources()
    {
        // 生成顶点数据
        generateVertices();

        // 绑定顶点数据
        GLES32.glGenVertexArrays(1, vaoIds, 0); // 生成 VAO
        GLES32.glBindVertexArray(vaoIds[0]);    // 绑定 VAO

        GLES32.glGenBuffers(1, vboIds, 0);
        GLES32.glBindBuffer(GLES32.GL_ARRAY_BUFFER, vboIds[0]);
        GLES32.glBufferData(GLES32.GL_ARRAY_BUFFER, boxVerticesBuffer.capacity() * Float.BYTES, boxVerticesBuffer, GLES32.GL_STATIC_DRAW);

        GLES32.glGenBuffers(1, eboIds, 0);
        GLES32.glBindBuffer(GLES32.GL_ELEMENT_ARRAY_BUFFER, eboIds[0]);
        GLES32.glBufferData(GLES32.GL_ELEMENT_ARRAY_BUFFER, boxIndicesBuffer.capacity() * Integer.BYTES, boxIndicesBuffer, GLES32.GL_STATIC_DRAW);

        GLES32.glEnableVertexAttribArray(0);
        GLES32.glVertexAttribPointer(0, 3, GLES32.GL_FLOAT, false, 6 * Float.BYTES, 0);
        GLES32.glEnableVertexAttribArray(1);
        GLES32.glVertexAttribPointer(1, 3, GLES32.GL_FLOAT, false, 6 * Float.BYTES, 3 * Float.BYTES);

        // 生成并编译着色器
        shaderProgramId = createAndCompileShaders(vsCode, gsCode, fsCode);
    }

    /**
     * @brief 绘制一帧
     */
    private void drawFrame()
    {
        GLES32.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        // 使用着色器程序
        GLES32.glUseProgram(shaderProgramId);

        // 设置模型矩阵
        Matrix.setIdentityM(m_modelMatrix, 0);
        Matrix.translateM(m_modelMatrix, 0, 0.0f, 0.0f, 0.0f);
        Matrix.rotateM(m_modelMatrix,0, 45.0f, 0.0f, 0.0f, 1.0f);
        Matrix.rotateM(m_modelMatrix,0, 30.0f, 0.0f, 1.0f, 0.0f);
        GLUniformManager.setUniformMatrix4fv(shaderProgramId, "u_ModelMatrix", m_modelMatrix);

        // 设置视图矩阵
        Matrix.setLookAtM(m_viewMatrix, 0,
                0.0f,0.0f,10.0f,
                0.0f,0.0f, 0.0f,
                0,1,0);
        // Matrix.setIdentityM(m_viewMatrix, 0);
        GLUniformManager.setUniformMatrix4fv(shaderProgramId, "u_ViewMatrix", m_viewMatrix);

        // 设置投影矩阵
        Matrix.perspectiveM(m_projectionMatrix, 0,
                65.0f, (float)width/height,
                0.1f, 100.0f);
        GLUniformManager.setUniformMatrix4fv(shaderProgramId, "u_ProjMatrix", m_projectionMatrix);

        // 计算经过的时间
        float elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0f;
        GLUniformManager.setUniform1fv(shaderProgramId, "u_time", elapsedTime);

        // 绘制
        GLES32.glDrawElements(GLES32.GL_TRIANGLES, 36, GLES32.GL_UNSIGNED_INT, 0);
    }

    /**
     * @brief 创建并编译着色器程序
     * @param vsCode 顶点着色器源代码
     * @param gsCode 几何着色器源代码
     * @param fsCode 片段着色器源代码
     * @return 着色器ID
     */
    private int createAndCompileShaders(String vsCode, String gsCode, String fsCode) {

        int vertexShader = loadShader(GLES32.GL_VERTEX_SHADER, vsCode);
        int geometryShader = loadShader(GLES32.GL_GEOMETRY_SHADER, gsCode);
        int fragmentShader = loadShader(GLES32.GL_FRAGMENT_SHADER, fsCode);

        // 创建程序并链接
        int programId = GLES32.glCreateProgram();
        GLES32.glAttachShader(programId, vertexShader);
        GLES32.glAttachShader(programId, geometryShader);
        GLES32.glAttachShader(programId, fragmentShader);
        GLES32.glLinkProgram(programId);

        // 清理着色器
        GLES32.glDeleteShader(vertexShader);
        GLES32.glDeleteShader(geometryShader);
        GLES32.glDeleteShader(fragmentShader);

        return programId;
    }

    private int loadShader(int type, String shaderCode) {
        int shader = GLES32.glCreateShader(type);
        GLES32.glShaderSource(shader, shaderCode);
        GLES32.glCompileShader(shader);

        // 检查编译状态
        int[] compiled = new int[1];
        GLES32.glGetShaderiv(shader, GLES32.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            String error = GLES32.glGetShaderInfoLog(shader);
            GLES32.glDeleteShader(shader); // 销毁着色器
            throw new RuntimeException("Error compiling shader: " + error);
        }

        return shader;
    }

}